// OpenCVDLL.h
/*
*  OpenCV C++/CLI Wrapper for Mayhem Application computer vision modules
*
* (c) 2010/2011 Microsoft Applied Sciences Group
*
* Author: Sven Kratz
*
* TODO: Split up into multiple header files!
*
*/


#pragma once

#define _HAS_ITERATOR_DEBUGGING 0

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <limits>

#include "videoInput.h"
#include <opencv/cv.h>
#include <opencv/cvaux.h>
#include <opencv/highgui.h>
#include <opencv2\objdetect\objdetect.hpp>
#include <opencv2\features2d\features2d.hpp>
#include <opencv2\calib3d\calib3d.hpp>
#include <opencv2\imgproc\imgproc_c.h>

#include "IMayhemVisionProcessor.h"

// Memory leaks? 
//#include <vld.h>

#undef CHECK_MEMORY

#ifdef CHECK_MEMORY
	// VS 2010 memory leak detection
#endif

#using <System.dll>
using namespace std;
using namespace cv;
using namespace System;
using namespace System::Threading;
using namespace System::Drawing; 
using namespace System::Diagnostics; 


#define DEBUG


// default buffer size for this library
#define BUF_SIZE 1024

namespace OpenCVDLL {

	

	/* Keypoints returned by SURF contain this information */
	public value struct SURFKeyPoint {

		 float x;
		 float y;
		 int laplacian;
		 int size;
		 float hessian;
		 float dir; 

	} ; 


	/* C# API wrapper for Presence detector */ 
	
	#define FACE_CASCADE "Packages\\MayhemOpenCVWrapper\\Resources\\haarcascade_frontalface_alt.xml"			// cascade to be loaded
	#define AVG_FRAMES 30																			// frames to be averaged over for face detection
	#define RADIUS 60																				// search radius for face detection registration
	#define FACE_MIN_SIZE 30																		// minimum face size to be considered

	public ref class PresenceDetector : public IMayhemVisionProcessor
	{
		private: 			
			int imgWidth;
			int imgHeight; 
			double sensitivity_;			// decay sensitivity for presence detector. Default: 0.2
			CvSize* imgSize; 
			// timeout until the detector is active
			bool IS_INITIALIZED; 

			// ===================== Motion Detection =====================
			IplImage * diffGray;
			IplImage * difference;
			IplImage * movingAverage;
			IplImage * activityHeatmap; 
			CvMemStorage* storage ;
			CvSeq * contour_; 
			IplImage * temp;
			CvPoint2D32f* cornersA;
			CvPoint2D32f* cornersB;
			bool first ;
			int * cBoundPoints;
			int numContours;
			//

			// =========================== Face Detection =======================	
			int avg_ctr; 
			int frame_count ; 
			std::vector<CvRect>** faces_hist; 
			std::vector<CvRect>* last_faces; 
			CvHaarClassifierCascade* cascade;
			//

			// ========================= end result of the calculation =====================
			bool presence; 
			//

			void DetectMotion(IplImage * image); 
			void DetectFaceAndDecidePresence(IplImage * image);
			bool Decide_Presence(std::vector<CvRect>& faces, IplImage * activity); 

		public:
			PresenceDetector(int width, int height) ;
			~PresenceDetector(); 
			virtual  void ProcessFrame(unsigned char * imageData) override ;		// implements IMayhemVisionProcessor
			bool GetCurrentPresence();												// has a presence been detected in the last frame
			cli::array<System::Drawing::Point^>^ GetCurrentPresenceLocations();			// gets the bounding boxes of detected faces 
			

			// module is initialized once AVG_FRAMES frames have been captures and
			// presence / not presence has been detected
			// expose this as a .net property
			property bool IsInitialized
			{
				bool get() { return frame_count > AVG_FRAMES;} 
			}

			// expose the decay sensitivity as a .net property
			property double Sensitivity
			{
				void set(double value) 
				{
					// allow only sane values to be set
					if (0.005 <= value && value <= 0.1)
					{
						sensitivity_ = value; 
					}
				}
				double get()
				{
					return sensitivity_; 
				}
			}

			// default sensitivity
			static const double DEFAULT_SENSITIVITY = 0.02; 

	}; 


	/* C# API wrapper for SURF object detector */

	public ref class SURFObjectDetector
	{
		private:
			int img_w, img_h;
			IplImage * currentImage;		// working image
			IplImage * templateImage;		// image for object to be detected --> should be set on init 
			CvSeq * imageKeypoints;
			CvSeq * imageDescriptors;
			
			vector<int> * ptpairs; 

			CvSeq * templateKeypoints; 
			CvSeq * templateDescriptors; 
			
			CvMemStorage * storage;				// memstorage for recurring operations (image descriptors) 

			CvMemStorage * templateDataStorage; // memstorage for template descriptors 

			// mutex for access to descriptor data
			// (the caret signifies that the Mutex object is garbage collected)
			static Mutex^ mut = gcnew Mutex; 

			// SURF parameters
			static const int SURF_HESSIAN_THRESH = 500;

			//bool initialized; 

			




			/* find pairs of matching points using fast approximate nearest neighbor (FLANN) */
			void flannFindPairs(	const CvSeq*, 
									const CvSeq* objectDescriptors,
									const CvSeq*, 
									const CvSeq* imageDescriptors, 
									vector<int> * ptpairs );

			/*  object location information: calculates a homography between original image feature points and object points */
			int locatePlanarObject( const CvSeq* templateKeypoints, 
									const CvSeq* templateDescriptors,
									const CvSeq* imageKeypoints, 
									const CvSeq* imageDescriptors,
									const CvPoint src_corners[4], CvPoint dst_corners[4] );

		 public: 

			SURFObjectDetector(int width, int height) ; 

			/* calculate SURF features for the template image */ 
			void AddTemplate(int t_width, int t_height, unsigned char * t_imageData) ; 

			/* image processing step: calculate SURF Feature descriptors of new image */
			void ProcessFrame(unsigned char * imageData) ; 

			/* retrieve keypoints and matching indices after image processing */
			void getKeypointsAndMatches(SURFKeyPoint * tKeyPoints, 
							int & numTemplateKeyPoints,
							SURFKeyPoint * iKeyPoints, 
							int & numImageKeyPoints, 
							int * pPairIndices, 
							int & numPointPairIndices) ;

			/* find matching object's corners */
			int findLastObjectCorners(int * cornerPoints); 
	};

	/* C# API wrapper to OpenCV functions for face detector Mayhem module */
	
	public ref class FaceDetector
	{
		private:
			IplImage * currentImage;
			IplImage * temp;
			CvMemStorage * storage; 
			CvHaarClassifierCascade * cascade; 
			static const char * cascade_name = FACE_CASCADE;
			bool initialized;
			bool first; 
		public:
			FaceDetector(int width, int height);
			~FaceDetector(); 
			void ProcessFrame(unsigned char * imageData, int * faceCoords, int  & numDetectedFaceCoords) ; 
	} ;



	/* C# API Wrapper to OpenCV functions for motion detector Mayhem module */
	public ref class MotionDetector
	{
		private:
			IplImage * diffGray;
			IplImage * difference;
			IplImage * movingAverage;
			IplImage * temp;		
			bool first;
			int w;
			int h; 	
		public:
			MotionDetector(int width, int height);
			~MotionDetector(); 
			void ProcessFrame(unsigned char * imageData, int * contourPoints, int & numContours);
	};

	/*
	* Low(est)-level C++ class that passes on camera frames to C# code 
	*/
	public class OpenCVImageProvider
	{
		private:
			videoInput  VI;
			bool threadRunning;

		public:
			 IplImage * global_image;
			 unsigned char * imageDataBuffer;
			 static OpenCVImageProvider instance;
			 bool initialized;
			 int width;
			 int height;
			 int imageSize;
			 vector<int> * ptpairs;

			 // Deprecated: only gests number of devices presnt
			 int GetNumDevices();

			 // this method is better than the above one -- it gets the number of devices
			 // and their names as a string
			 void EnumerateDevices(char * devices, int * numDevices);

			 void InitCam(int device, int width, int height);

			 void StopCam(int device);

			 void GetNextFrame(int deviceID, unsigned char * buffer); 
			
			 int GetImageSize();			
	};


	/*
	*  low-level C# Wrapper API for communicating with the camera
	*/
	public ref class OpenCVBindings
	{

		private:
			static  OpenCVImageProvider * i;
			static bool provider_initialized = false;
		
		public:

			static void Initialize()
			{
				printf("InitOpenCVBindings\n");
				i = new OpenCVImageProvider();
				printf("Cam Initialized\n");
				provider_initialized = true;
			}

			static void EnumerateDevices(char * device_list, int * numDevices)
			{
				if (provider_initialized)
				{
					printf("Enumerating Devices \n");
					i->EnumerateDevices(device_list, numDevices);
				}
				else
				{
					printf("Provider is not initialized\n");
				}
			}

			static void StopCamera(int deviceId)
			{
				if (provider_initialized)
				{
					//provider_initialized = false;
					printf("Stopping Cam %d\n", deviceId);
					i->StopCam(deviceId);
				}
				else
				{
					printf("Provider is not initialized\n");
				}
			}

			static bool InitCapture(int deviceId, int cWidth, int cHeight)
			{
				printf("InitOpenCVBindings\n");
				
				if (provider_initialized)
				{
					try 
					{
						i->InitCam(deviceId,cWidth,cHeight);
						printf("Cam Initialized\n");
						return true;
					}
					catch (System::AccessViolationException^ avEx)
					{
						Debug::WriteLine("OpenCVBindings InitCapture Exception:"); 
						Debug::WriteLine(avEx);
					}
				}
				else
				{
					printf("Provider not initialized\n");
					return false;
				}
			
			}

			/*
			*  Request next frame from imaging device with deviceID
			*/
			static void GetNextFrame(int deviceID, unsigned char * imageDataBuffer)
			{
				try
				{
					i->GetNextFrame(deviceID,imageDataBuffer);
				}
				catch (System::Exception^ ex)
				{
					Debug::WriteLine("OpenCVBindings GetNextFrame Exception:");
					Debug::WriteLine(ex);
				}
			}

			static int GetImageSize()
			{
				/*
				* Return image size nr of bytes
				*/
				int size = i->GetImageSize();
				printf("GetImageSize : size is %d\n",size);
				return size;
			}		
	};

}
